package com.katesoft.scale4j.persistent.hibernate;

import com.katesoft.scale4j.log.Logger;
import com.katesoft.scale4j.log.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.cfg.Configuration;
import org.hibernate.envers.event.AuditEventListener;
import org.hibernate.event.Destructible;
import org.hibernate.event.FlushEventListener;
import org.hibernate.event.Initializable;
import org.hibernate.event.MergeEventListener;
import org.hibernate.event.PostCollectionRecreateEventListener;
import org.hibernate.event.PostCollectionRemoveEventListener;
import org.hibernate.event.PostCollectionUpdateEventListener;
import org.hibernate.event.PostDeleteEventListener;
import org.hibernate.event.PostInsertEventListener;
import org.hibernate.event.PostUpdateEventListener;
import org.hibernate.event.PreCollectionRemoveEventListener;
import org.hibernate.event.PreCollectionUpdateEventListener;
import org.hibernate.event.SaveOrUpdateEvent;
import org.hibernate.event.SaveOrUpdateEventListener;
import org.hibernate.event.def.DefaultFlushEventListener;
import org.hibernate.event.def.DefaultSaveEventListener;
import org.hibernate.event.def.DefaultSaveOrUpdateEventListener;
import org.hibernate.event.def.DefaultUpdateEventListener;
import org.hibernate.search.event.FullTextIndexEventListener;
import org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;

/**
 * This is internal class which is responsible for creating consolidated event listeners collection combining user's event listeners and hibernate search and
 * hibernate envers listeners.
 *
 * @author kate2007
 */
@SuppressWarnings({"unchecked"})
public class EventListeners implements Initializable, Destructible
{
    public static final String SAVE = "save";
    public static final String UPDATE = "update";
    public static final String FLUSH = "flush";
    public static final String SAVE_UPDATE = "save-update";
    public static final String MERGE = "merge";
    public static final String POST_INSERT = "post-insert";
    public static final String POST_UPDATE = "post-update";
    public static final String POST_DELETE = "post-delete";
    public static final String PRE_COLLECTION_UPDATE = "pre-collection-update";
    public static final String PRE_COLLECTION_REMOVE = "pre-collection-remove";
    public static final String POST_COLLECTION_REMOVE = "post-collection-remove";
    public static final String POST_COLLECTION_UPDATE = "post-collection-update";
    public static final String POST_COLLECTION_RECREATE = "post-collection-recreate";
    //
    private Logger logger = LogFactory.getLogger(EventListeners.class);
    private AuditEventListener auditEventListener = new LocalAuditEventListener();
    private FullTextIndexEventListener fullTextIndexEventListener = new FullTextIndexEventListener(FullTextIndexEventListener.Installation.SINGLE_INSTANCE);
    private Map<String, Object> providedListeners;

    public EventListeners(Map<String, Object> providedListeners)
    {
        this.providedListeners = (providedListeners != null ? providedListeners : Collections.<String, Object>emptyMap());
    }

    @SuppressWarnings("serial")
    public Map<String, Object> listeners()
    {
        Map<String, Object> actualListeners = new HashMap<String, Object>(providedListeners);
        //
        addSaveListeners(actualListeners);
        addFlushListeners(actualListeners);
        addUpdateListeners(actualListeners);
        addSaveOrUpdateListeners(actualListeners);
        addMergeListeners(actualListeners);
        addPostInsertListeners(actualListeners);
        addPostUpdateListeners(actualListeners);
        addPostDeleteListeners(actualListeners);
        addPreCollectionUpdateListeners(actualListeners);
        addPreCollectionRemoveListeners(actualListeners);
        addPostCollectionRemoveListeners(actualListeners);
        addPostCollectionUpdateListeners(actualListeners);
        addPostCollectionRecreateListeners(actualListeners);
        //
        return actualListeners;
    }

    @Override
    public void initialize(Configuration configuration)
    {
        auditEventListener.initialize(configuration);
        fullTextIndexEventListener.initialize(configuration);
        logger.debug("audit_listener=%s initialized using configuration=%s", auditEventListener, configuration);
        logger.debug("fullTextIndexEventListener=%s initialized using configuration=%s", auditEventListener, configuration);
    }

    @Override
    public void cleanup()
    {
        fullTextIndexEventListener.cleanup();
    }

    /**
     * add save updated listeners(client should not carry about DefaultSaveEventListener, this event listener will be added as well)
     *
     * @param temp collection of event listeners
     */
    protected void addSaveListeners(Map<String, Object> temp)
    {
        final Collection<Object> provided = (Collection<Object>) providedListeners.get(SAVE);
        temp.put(SAVE, new LinkedHashSet<SaveOrUpdateEventListener>()
        {{
                if (provided != null) {
                    for (Object next : provided) {
                        add((SaveOrUpdateEventListener) next);
                    }
                }
                add(new DefaultSaveEventListener()
                {
                    @Override
                    public void onSaveOrUpdate(SaveOrUpdateEvent event)
                    {
                        logger.trace("onSave[%s]", event.getObject());
                        super.onSaveOrUpdate(event);
                    }
                });
            }});
    }

    /**
     * add flush listeners(client should not carry about DefaultFlushEventListener, this event listener will be added as well)
     *
     * @param temp collection of event listeners
     */
    protected void addFlushListeners(Map<String, Object> temp)
    {
        final Collection<Object> provided = (Collection<Object>) providedListeners.get(FLUSH);
        temp.put(FLUSH, new LinkedHashSet<FlushEventListener>()
        {{
                if (provided != null) {
                    for (Object next : provided) {
                        add((FlushEventListener) next);
                    }
                }
                add(fullTextIndexEventListener);
                add(new DefaultFlushEventListener());
            }});
    }

    /**
     * add update listeners(client should not carry about DefaultUpdateEventListener, this event listener will be added as well)
     *
     * @param temp collection of event listeners
     */
    protected void addUpdateListeners(Map<String, Object> temp)
    {
        final Collection<Object> provided = (Collection<Object>) providedListeners.get(UPDATE);
        temp.put(UPDATE, new LinkedHashSet<SaveOrUpdateEventListener>()
        {
            {
                if (provided != null) {
                    for (Object next : provided) {
                        add((SaveOrUpdateEventListener) next);
                    }
                }
                add(new DefaultUpdateEventListener()
                {
                    @Override
                    public void onSaveOrUpdate(SaveOrUpdateEvent event)
                    {
                        logger.trace("onUpdate[%s]", event.getObject());
                        super.onSaveOrUpdate(event);
                    }
                });
            }
        });
    }

    /**
     * add save-or-update listeners(client should not carry about DefaultSaveOrUpdateEventListener, this event listener will be added as well)
     *
     * @param temp collection of event listeners
     */
    protected void addSaveOrUpdateListeners(Map<String, Object> temp)
    {
        final Collection<Object> provided = (Collection<Object>) providedListeners.get(SAVE_UPDATE);
        temp.put(SAVE_UPDATE, new LinkedHashSet<SaveOrUpdateEventListener>()
        {{
                if (provided != null) {
                    for (Object next : provided) {
                        add((SaveOrUpdateEventListener) next);
                    }
                }
                add(new DefaultSaveOrUpdateEventListener()
                {
                    @Override
                    public void onSaveOrUpdate(SaveOrUpdateEvent event) throws HibernateException
                    {
                        logger.trace("onSaveOrUpdate[%s]", event.getObject());
                        super.onSaveOrUpdate(event);
                    }
                });
            }});
    }

    /**
     * add merge listeners(client should not carry about DefaultMergeEventListener, this event listener will be added as well)
     *
     * @param temp collection of event listeners
     */
    protected void addMergeListeners(Map<String, Object> temp)
    {
        final Collection<Object> provided = (Collection<Object>) providedListeners.get(MERGE);
        temp.put(MERGE, new LinkedHashSet<MergeEventListener>()
        {{
                if (provided != null) {
                    for (Object next : provided) {
                        add((MergeEventListener) next);
                    }
                }
                add(new IdTransferringMergeEventListener());
            }});
    }

    protected void addPostInsertListeners(Map<String, Object> temp)
    {
        final Collection<Object> provided = (Collection<Object>) providedListeners.get(POST_INSERT);
        temp.put(POST_INSERT, new LinkedHashSet<PostInsertEventListener>()
        {{
                if (provided != null) {
                    for (Object next : provided) {
                        add((PostInsertEventListener) next);
                    }
                }
                add(auditEventListener);
                add(fullTextIndexEventListener);
            }});
    }

    protected void addPostUpdateListeners(Map<String, Object> temp)
    {
        final Collection<Object> provided = (Collection<Object>) providedListeners.get(POST_UPDATE);
        temp.put(POST_UPDATE, new LinkedHashSet<PostUpdateEventListener>()
        {{
                if (provided != null) {
                    for (Object next : provided) {
                        add((PostUpdateEventListener) next);
                    }
                }
                add(auditEventListener);
                add(fullTextIndexEventListener);
            }});
    }

    protected void addPostDeleteListeners(Map<String, Object> temp)
    {
        final Collection<Object> provided = (Collection<Object>) providedListeners.get(POST_DELETE);
        temp.put(POST_DELETE, new LinkedHashSet<PostDeleteEventListener>()
        {{
                add(auditEventListener);
                add(fullTextIndexEventListener);
                if (provided != null) {
                    for (Object next : provided) {
                        add((PostDeleteEventListener) next);
                    }
                }
            }});
    }

    protected void addPreCollectionUpdateListeners(Map<String, Object> temp)
    {
        final Collection<Object> provided = (Collection<Object>) providedListeners.get(PRE_COLLECTION_UPDATE);
        temp.put(PRE_COLLECTION_UPDATE, new LinkedHashSet<PreCollectionUpdateEventListener>()
        {{
                add(auditEventListener);
                if (provided != null) {
                    for (Object next : provided) {
                        add((PreCollectionUpdateEventListener) next);
                    }
                }
            }});
    }

    protected void addPreCollectionRemoveListeners(Map<String, Object> temp)
    {
        final Collection<Object> provided = (Collection<Object>) providedListeners.get(PRE_COLLECTION_REMOVE);
        temp.put(PRE_COLLECTION_REMOVE, new LinkedHashSet<PreCollectionRemoveEventListener>()
        {{
                add(auditEventListener);
                if (provided != null) {
                    for (Object next : provided) {
                        add((PreCollectionRemoveEventListener) next);
                    }
                }
            }});
    }

    protected void addPostCollectionRemoveListeners(Map<String, Object> temp)
    {
        final Collection<Object> provided = (Collection<Object>) providedListeners.get(POST_COLLECTION_REMOVE);
        temp.put(POST_COLLECTION_REMOVE, new LinkedHashSet<PostCollectionRemoveEventListener>()
        {{
                add(fullTextIndexEventListener);
                if (provided != null) {
                    for (Object next : provided) {
                        add((PostCollectionRemoveEventListener) next);
                    }
                }
            }});
    }

    protected void addPostCollectionUpdateListeners(Map<String, Object> temp)
    {
        final Collection<Object> provided = (Collection<Object>) providedListeners.get(POST_COLLECTION_UPDATE);
        temp.put(POST_COLLECTION_UPDATE, new LinkedHashSet<PostCollectionUpdateEventListener>()
        {{
                add(fullTextIndexEventListener);
                if (provided != null) {
                    for (Object next : provided) {
                        add((PostCollectionUpdateEventListener) next);
                    }
                }
            }});
    }

    protected void addPostCollectionRecreateListeners(Map<String, Object> temp)
    {
        final Collection<Object> provided = (Collection<Object>) providedListeners.get(POST_COLLECTION_RECREATE);
        temp.put(POST_COLLECTION_RECREATE, new LinkedHashSet<PostCollectionRecreateEventListener>()
        {{
                add(auditEventListener);
                add(fullTextIndexEventListener);
                if (provided != null) {
                    for (Object next : provided) {
                        add((PostCollectionRecreateEventListener) next);
                    }
                }
            }});
    }
}
